import logging
import numpy as np
import pyqtgraph as gr
from graphics import curves
from misc import misc
import block


class DrawerInput(block.Input):
    """Will be used as inputs of drawer blocks.
    
    Has been modified from original Input to tell the Block to
    register/unregister data."""
    def __setitem__(self, key, value):
        """Overriden to tell block to unregister/register data."""
        if self._dataDict.has_key(key):
            self._block._UnregisterTimeData(self._dataDict[key])
        block.Input.__setitem__(self, key, value)
        self._block._RegisterTimeData(value)



class Drawer(block.IBlock):
    """Block that draws something. Non-visual.
    
    --->|(input) Drawer |
    
    It manages Curve objects but they get added to the plot.
    """
    
    # (Read-only)
    @property
    def curves(self):
        return self._curves
        
    # (Read-only) Short for curves[0]
    @property
    def curve(self):
        return self._curves[0]
    
    def _AddTerminals(self):
        self._AddInput("input", DrawerInput)

    def _RegisterTimeData(self, td):
        self._td = td
        self._plot._RegisterTimeData(td)
    
    def _UnregisterTimeData(self, td):
        self._plot._UnregisterTimeData(td)
    
    def __init__(self, plot):
        """Connects with a plots.NavalaPlot."""
        block.Block.__init__(self)
        self._plot = plot
        # _curves facility, i.e., not really essential that descendants stick to it
        self._curves = {}
        plot._AddDrawer(self)

    def GetCrosshairTuples(self, x):
        """Override this if the drawer reports anything on mouse move.
        
        Must return a list [("name0", "value0"), ...]
        
        Arguments:
            x -- mouse coordinate to be searched into some timestamp2."""
        return []

    def _Update(self):
        """Called after throughout data assignment.
        
        Override this if you want to update something internally in the graphics
        framework before painting again (this is just a provision ATM).
        
        Note that this is not intended to trigger any repaint.
        """
        pass

    def x2index(self, x):
        """Facility - routine to search x from within TimeData.timestamp2"""
        return self._x2index(self["input"][0].timestamp2, x)

    def _x2index(self, ts2, x):
        """Facility to unify the way x is converted into index.
        
        Arguments:
            ts2 -- a timestamp2 vector
            x -- a mouse coordinate
        """
        return misc.BSearchRound(ts2, x)

    def _AddCurve(self, curve):
        """Part of "curves" facility."""
        def FindKey():
            i = 0
            while self._curves.has_key(i):
                i += 1
            return i
        self._plot.AddCurve(curve)
        key = curve.name if curve.name is not None else FindKey()
        self._curves[key] = curve
        
################################################################################

class CurveDrawer(Drawer):
    """Drawer of a single curve.
    
    Descendants require overriding _GetCurve() only."""
    
    def __init__(self, plot):
        Drawer.__init__(self, plot)
        # Note that when the Curve is created, it calls its Drawer _AddCurve() method.
        # So, the call to _GetCurve() is just like this:
        self._GetCurve()

    def _GetCurve(self):
        """Abstract. Implement this to call _AddCurve() once."""
        raise NotImplementedError("Not implemented")
    
    def _Use(self):
        self._curves[0].SetData(self._td)

#####        view = self._curve.getViewBox()
#####        if view is not None:
#####            view.update()
#####            view.itemBoundsChanged(self)
#####        self._curve.prepareGeometryChange()
#####        self._curve.update() #SetTimeData(self._td)
#####        self._curve.informViewBoundsChanged()


class QuoteDrawer(CurveDrawer):
    pass
    
class PriceDrawer(QuoteDrawer):
    """Base for candlestick, bar etc price representations."""
    def GetCrosshairTuples(self, x):
        index = self.x2index(x)
        q = self._td
        return [("open", "%.2f" % q.open[index]),
                ("close", "%.2f" % q.close[index]),
                ("high", "%.2f" % q.high[index]),
                ("low", "%.2f" % q.low[index])]
    
class CandlestickDrawer(PriceDrawer):
    def _GetCurve(self):
        return curves.CandlestickCurve(self)

# TODO
class BarDrawer(PriceDrawer):
    pass

class VolumeDrawer(QuoteDrawer):
    def _GetCurve(self):
        return curves.VolumeCurve(self)

    def GetCrosshairTuples(self, x):
        index = self.x2index(x)
        q = self._td
        return [("volume", "%d" % q.volume[index]),
                ("num trades", "%d" % q.numTrades[index])]


class PathDrawer(CurveDrawer):
    """Draws single line based on one of the fields of a TimeData."""

    # This property wraps _curve.fieldName
    @property
    def fieldName(self):
        return self._curve.fieldName
    @fieldName.setter
    def fieldName(self, x):
        self._curves[0].fieldName = x

    def _GetCurve(self):
        return curves.Path(self)

    def _Use(self):
        """Overriden to set curve fieldName before calling ancestor."""
        self._curves[0].fieldName = self["input"].fieldName
        CurveDrawer._Use(self)


class PathHLinesDrawer(PathDrawer):
    """Path Drawer plus set of infinite horizontal lines.
    
    Line positions are defined by setting the self.horizontalPositions field.
    
    Keyword arguments:
        numLines=2 -- number of horizontal lines. They will be created upon initialization.
        linePositions = [] -- line positions. May be set later with SetLinePosition()
    """
    
    @property
    def numLines(self):
        return self.__numLines
    
    def __init__(self, plot, **kwargs):
        PathDrawer.__init__(self, plot)
        numLines = kwargs.get("numLines", 2)
        for i in range(numLines):
            curves.HLine(self, pen=(192, 192, 192))
        if kwargs.has_key("linePositions"):
            for i in range(numLines):
                self.SetLinePosition(i, kwargs["linePositions"][i])
    
    def SetLinePosition(self, i, value):
        self._curves[i+1].setPos(value)
        

##################class GenericTimeDataIndicatorDrawer(PathDrawer):
##################    ## Setup
##################    # TimeDataInticator. Must set this.
##################    indicator = None
##################    def _OnSetTimeData(self):
##################        
################### TODO: I have to do better than this. Like this, it is not possible to
################### chain the drawers. There is something hidden in my model that I haven't
################### found out yet. Maybe when I do the MACD? No, Oscillator maybe?
################### The funny thing is that the Drawer isn't really acting like one.
################### I have to choose a more complex drawing, that uses more than a curve.
################### Whether it is the MACD, I don't know; but let's find something. Maybe
################### some kind of oscillator.
##################
##################        # TODO: obviously another HACK, just to keep it going until I sort this
##################        anotherTD = self.indicator.GetTimeData(self._td)
##################        anotherTD.timestamp2 = np.copy(self._td.timestamp2[-len(anotherTD):])
##################        
##################        self._curves[0].SetTimeData(anotherTD)
##################
##################
##################    def _GetCurve(self):
##################        PathDrawer._GetCurve(self)
##################        self._curves[0].fieldName = "y" # TODO: Another bit of a hack, just to see if I can get it plotting

#
#class MACDDrawer(Drawer):
#    - Actually I need to define a 
